﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using BacteriaSimulation.Simulation;
using System.Threading;
using BacteriaSimulation.GUI;
using System.Windows.Media.Animation;

namespace BacteriaSimulation
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        protected delegate void updateDelegateEventHandler(SimulationUpdateEventArgs args);
        protected delegate void deathDelegateEventHandler(BacteriumUpdateEventArgs args);
        protected SimulationEnvironment Env { get; set; }
        protected Simulation.Simulation Simulation { get; set; }
        protected Thread simulationThread;

        protected Point clippingRectOrigin;
        protected Rectangle clippingRectangle;
        protected List<Line> gridLines;
        protected Polygon resizePolygon;
        protected Point resizeInitialPosition;

        protected Dictionary<Bacterium, BacteriumView> viewDictonary;
        protected SimulationState guiState { get; set; }


        public MainWindow()
        {
            InitializeComponent();
            StopButton.IsEnabled = false;
            AddButton.IsEnabled = false;
            AddCannibalBacteriumButton.IsEnabled = false;
            guiState = SimulationState.STOPPED;
            this.Closing += MainWindow_Closing;
            this.chkDrawGrid.Unchecked += chkDrawGrid_Checked;
            this.MouseMove += MouseMoveForResize;
            this.MouseLeftButtonDown += MouseLeftButtonDownForResize;
            this.SizeChanged += MainWindow_SizeChanged;

            cmbDetectCollision.Items.Add("true");
            cmbDetectCollision.Items.Add("false");

            txtMaxEnergy.Text = SimulationEnvironment.MAX_ENERGY.ToString();
            txtInitialEnergy.Text = SimulationEnvironment.INITIAL_ENERGY.ToString();
            txtCyanobacteriumSpeed.Text = SimulationEnvironment.MAX_SPEED_COMPONENT.ToString();
            txtCyanoEnergyInc.Text = SimulationEnvironment.ENERGY_INCREMENT.ToString();

            txtCannibalMaxEnergy.Text = "90";
            txtCannibalInitialEnergy.Text = "90";
            txtCannibalBacteriumSpeed.Text = "80";
            txtCannibalEnergyInc.Text = "0,05";

            txtMutationProbability.Text = SimulationEnvironment.MUTATION_PROBABILITY.ToString();

            this.gridLines = new List<Line>();
        }

        void MainWindow_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            double newWidth = this.Width - SimulationCanvas.Margin.Left - 30;
            double newHeight = this.Height - controlCanvas.Height - SimulationCanvas.Margin.Top - 30;
            if (newWidth > 0)
            {
                SimulationCanvas.Width = newWidth;
            }
            if (newHeight > 0)
            {
                SimulationCanvas.Height = newHeight;
            }
            //this.lblAvalaibleResources.Content = (SimulationCanvas.Width + ", " + SimulationCanvas.Height + ", " + controlCanvas.Margin.Top);
            double top = this.SimulationCanvas.Margin.Top + this.SimulationCanvas.Height;
            controlCanvas.Margin = new Thickness(controlCanvas.Margin.Left, top, controlCanvas.Margin.Right, controlCanvas.Margin.Bottom);
        }

        void MouseMoveForResize(Object sender, MouseEventArgs args)
        {
            Point pointerPosition = args.GetPosition(this);
            if (pointerPosition.X < SimulationCanvas.Margin.Left + SimulationCanvas.Width
                && pointerPosition.Y < SimulationCanvas.Margin.Left + SimulationCanvas.Height
                && pointerPosition.X > SimulationCanvas.Margin.Left + SimulationCanvas.Width - 20 
                && pointerPosition.Y > SimulationCanvas.Margin.Left + SimulationCanvas.Height - 20)
            {
                this.Cursor = Cursors.SizeNWSE;
                //this.resizePolygon = new Polygon();
                //this.resizePolygon.Points.Add(new Point(SimulationCanvas.Width, SimulationCanvas.Height));
                //this.resizePolygon.Points.Add(new Point(SimulationCanvas.Width, SimulationCanvas.Height - 50));
                //this.resizePolygon.Points.Add(new Point(SimulationCanvas.Width - 50, SimulationCanvas.Height));
                //this.resizePolygon.Stroke = Brushes.Black;
                this.lblAvalaibleResources.Content = "RESIZE!";
                //this.SimulationCanvas.Children.Add(resizePolygon);
            }
            else
            {
                this.Cursor = Cursors.Arrow;
                this.lblAvalaibleResources.Content = "";
                //this.SimulationCanvas.Children.Remove(resizePolygon);
            }
        }

        private void MouseLeftButtonDownForResize(object sender, MouseButtonEventArgs e)
        {
            Point pointerPosition = e.GetPosition(this);
            if (pointerPosition.X < SimulationCanvas.Margin.Left + SimulationCanvas.Width
               && pointerPosition.Y < SimulationCanvas.Margin.Left + SimulationCanvas.Height
               && pointerPosition.X > SimulationCanvas.Margin.Left + SimulationCanvas.Width - 50
               && pointerPosition.Y > SimulationCanvas.Margin.Left + SimulationCanvas.Height - 50)
            {
                this.MouseMove -= MouseMoveForResize;
                Point position = e.GetPosition(this);
                resizeInitialPosition = position;
                this.MouseMove += MouseMoveResizing;
                this.MouseLeftButtonUp += MouseLeftButtonUpResizing;
            }
        }

        private void MouseLeftButtonUpResizing(object sender, MouseButtonEventArgs e)
        {
            this.MouseMove -= MouseMoveResizing;
            this.MouseLeftButtonUp -= MouseLeftButtonUpResizing;
            this.MouseMove += MouseMoveForResize;
        }

        private void MouseMoveResizing(object sender, MouseEventArgs e)
        {
            Point position = e.GetPosition(this);
            Vector shift = new Vector(position.X - resizeInitialPosition.X, position.Y - resizeInitialPosition.Y);

            this.SimulationCanvas.Width = position.X - SimulationCanvas.Margin.Left;
            this.SimulationCanvas.Height = position.Y - SimulationCanvas.Margin.Top;

            double top = this.SimulationCanvas.Margin.Top + this.SimulationCanvas.Height;
            //lblAvalaibleResources.Content = top + " = " + SimulationCanvas.Margin.Top + " + " + this.SimulationCanvas.Height;
            controlCanvas.Margin = new Thickness(controlCanvas.Margin.Left, top, controlCanvas.Margin.Right, controlCanvas.Margin.Bottom);
            //Canvas.SetLeft(resizePolygon, shift.X);
            //Canvas.SetTop(resizePolygon, shift.Y);

            //Canvas.SetLeft(resizePolygon, SimulationCanvas.Width - 50);
            //Canvas.SetTop(resizePolygon, SimulationCanvas.Height - 50);
        }

        void SimulationCanvas_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (guiState != SimulationState.RUNNING)
            {
                return;
            }
            Point clickPoint = e.GetPosition(SimulationCanvas);
            Double speed = Double.Parse(txtCannibalBacteriumSpeed.Text);

            Bacterium bact = new Bacterium(this.Simulation, new SimulationPoint(clickPoint.X, clickPoint.Y), Vector2D.GetRandomVector2D(1), speed, 850,
                double.Parse(txtCannibalInitialEnergy.Text),
                new CannibalBacteriumStrategy(), double.Parse(txtCannibalMaxEnergy.Text), double.Parse(txtCannibalEnergyInc.Text));
            if (bact.X > Env.Width - bact.Width)
            {
                bact.X = Env.Width - bact.Width - Env.Epsilon;
            }
            if (bact.Y > Env.Height - bact.Height)
            {
                bact.Y = Env.Height - bact.Height - Env.Epsilon;
            }
            SimulationCommand cmd = new AddCommand(bact);
            AddBacteriumView(bact, Brushes.Red);
            Simulation.EnqueueCommand(cmd);
            bact.Death += bact_Death;
        }

        void SimulationCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            Point origin = e.GetPosition(SimulationCanvas);
            clippingRectOrigin = origin;
            this.SimulationCanvas.MouseMove += SimulationCanvas_MouseMove;
            this.SimulationCanvas.MouseLeftButtonUp += SimulationCanvas_MouseLeftButtonUp;
            if(SimulationCanvas.Children.Contains(clippingRectangle))
            {
                SimulationCanvas.Children.Remove(clippingRectangle);
            }
            this.clippingRectangle = new Rectangle();
        }

        void SimulationCanvas_MouseMove(object sender, MouseEventArgs e)
        {
            try
            {
                Point mousePosition = e.GetPosition(SimulationCanvas);
                Point rectangleOrigin = new Point(clippingRectOrigin.X, clippingRectOrigin.Y);
                if(mousePosition.X < clippingRectOrigin.X)
                {
                    rectangleOrigin.X = mousePosition.X;
                }
                if (mousePosition.Y < clippingRectOrigin.Y)
                {
                    rectangleOrigin.Y = mousePosition.Y;
                }

                Double width = Math.Abs( mousePosition.X - clippingRectOrigin.X );
                Double height = Math.Abs( mousePosition.Y - clippingRectOrigin.Y);

                clippingRectangle.Stroke = Brushes.Black;
                Canvas.SetLeft(clippingRectangle, rectangleOrigin.X);
                Canvas.SetTop(clippingRectangle, rectangleOrigin.Y);
                clippingRectangle.Width = width;
                clippingRectangle.Height = height;
                if(!SimulationCanvas.Children.Contains(clippingRectangle))
                {
                    SimulationCanvas.Children.Add(clippingRectangle);
                }
            }
            catch (Exception ex)
            {
                System.Console.WriteLine(ex.Message);
            }
        }

        void SimulationCanvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            this.SimulationCanvas.MouseMove -= SimulationCanvas_MouseMove;
            this.SimulationCanvas.Children.Remove(clippingRectangle);
            KillRangeCommand cmd = new KillRangeCommand(Canvas.GetLeft(clippingRectangle), Canvas.GetTop(clippingRectangle), clippingRectangle.Width, clippingRectangle.Height);
            Simulation.EnqueueCommand(cmd);
        }

        void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            if (this.Simulation != null)
            {
                this.Simulation.EnqueueCommand(new SimulationCommand("stop"));
                simulationThread.Abort();
            }
        }

        private void StartButton_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                this.MouseMove -= MouseMoveForResize;
                this.MouseLeftButtonDown -= MouseLeftButtonDownForResize;

                this.SimulationCanvas.MouseLeftButtonDown += SimulationCanvas_MouseLeftButtonDown;
                this.SimulationCanvas.MouseRightButtonDown += SimulationCanvas_MouseRightButtonDown;

                guiState = SimulationState.RUNNING;
                Double initialResourceEnergy = Double.Parse(initialMassResourceTextBox.Text);
                Double mutationProbability = Double.Parse(txtMutationProbability.Text);
                SimulationEnvironment env = new SimulationEnvironment(this.SimulationCanvas.Width, this.SimulationCanvas.Height, initialResourceEnergy, mutationProbability, false);
                this.Env = env;
                // collects initialization parameters
                try
                {
                    env.MaxEnergy = double.Parse(txtMaxEnergy.Text);
                    env.InitialEnergy = double.Parse(txtInitialEnergy.Text);
                    env.EnergyIncrement = double.Parse(txtCyanoEnergyInc.Text);
                    env.DetectBacteriaCollision = Boolean.Parse(cmbDetectCollision.SelectedValue.ToString());
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message);
                }

                int rowCount = int.Parse(rowCountTxt.Text);
                int columnCount = int.Parse(columnCountTxt.Text);

                Simulation.Simulation simulation = new Simulation.Simulation(env, rowCount, columnCount);
                simulation.SimulationUpdate += simulation_SimulationUpdate;

                viewDictonary = new Dictionary<Bacterium, BacteriumView>();

                Simulation = simulation;
                System.Threading.Thread thread = new System.Threading.Thread(simulation.SimulationThreadStart);
                this.simulationThread = thread;
                this.StartButton.IsEnabled = false;
                this.StopButton.IsEnabled = true;
                this.AddButton.IsEnabled = true;
                this.AddCannibalBacteriumButton.IsEnabled = true;

                if ((bool)chkDrawGrid.IsChecked)
                {
                    DrawGrid();
                }

                thread.Start();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        protected void DrawGrid()
        {
            ClearGrid();
            this.gridLines.Clear();

            double cellWidth = Env.Width / Simulation.Grid.ColumnCount;
            double cellHeight = Env.Height / Simulation.Grid.RowCount;
            // Draws vertical lines
            double currentX = cellWidth;
            while (currentX <= Env.Width)
            {
                Line vertical = new Line();
                vertical.Stroke = Brushes.Black;
                vertical.X1 = currentX;
                vertical.X2 = currentX;
                vertical.Y1 = 0;
                vertical.Y2 = Env.Height;
                SimulationCanvas.Children.Add(vertical);
                this.gridLines.Add(vertical);

                currentX += cellWidth;
            }
            // Draws horizontal lines
            double currentY = cellHeight;
            while (currentY <= Env.Height)
            {
                Line horizontal = new Line();
                horizontal.Stroke = Brushes.Black;
                horizontal.X1 = 0;
                horizontal.X2 = Env.Width;
                horizontal.Y1 = currentY;
                horizontal.Y2 = currentY;
                SimulationCanvas.Children.Add(horizontal);
                this.gridLines.Add(horizontal);

                currentY += cellHeight;
            }
        }

        protected void ClearGrid()
        {
            foreach (Line line in gridLines)
            {
                SimulationCanvas.Children.Remove(line);
            }
            gridLines.Clear();
        }

        void simulation_SimulationUpdate(SimulationUpdateEventArgs args)
        {
            if (guiState == SimulationState.STOPPED)
            {
                return;
            }
            Object[] functionArgs = new Object[1];
            functionArgs[0] = args;
            updateDelegateEventHandler updateDelegate = UpdateView;

            // "begin" is used to calculate how long it takes to repaint the canvas.
            DateTime begin = DateTime.Now;

            this.Dispatcher.Invoke(updateDelegate, System.Windows.Threading.DispatcherPriority.Background, functionArgs);
            if (guiState == SimulationState.STOPPED)
            {
                clearCanvas();
            }
            DateTime end =DateTime.Now;
            TimeSpan howLong = end.Subtract(begin);
            TimeSpan simulationInterval = new TimeSpan(0, 0, 0, 0, Env.SimulationStepInterval);
            if (howLong.CompareTo(simulationInterval) == -1)
            {
                System.Threading.Thread.Sleep(simulationInterval.Subtract(howLong));
            }

        }

        public void UpdateView(SimulationUpdateEventArgs args)
        {
            foreach (Bacterium b in args.Bacteria)
            {
                UpdateBacteriumView(b);
            }
            this.cyanoCountLabel.Content = args.CyanobacteriaCount;
            this.cannibalCountLabel.Content = args.CannibalBacteriaCount;
            this.lblAvalaibleResources.Content = "Avalaible Energy Resources: " + args.AvalaibleResources.ToString();
        }

        protected void UpdateBacteriumView(Bacterium bacterium)
        {
            BacteriumView view = null;

            if (!this.viewDictonary.ContainsKey(bacterium) )
            {
                if (bacterium.State != BacteriumState.DEAD)
                {
                    Brush fill = null;
                    if (bacterium.Strategy is CannibalBacteriumStrategy)
                    {
                        fill = Brushes.Red;
                    }
                    view = AddBacteriumView(bacterium, fill);
                    bacterium.Death += bact_Death;
                }
                else
                {
                    return;
                }
            }
            else
            {
                if (bacterium.State == BacteriumState.DEAD)
                {
                    RemoveBacteriumView(bacterium);
                    return;
                }
                else
                {
                    view = this.viewDictonary[bacterium];
                }
            }

            view.AdjustSize(bacterium.Width, bacterium.Height);
            Canvas.SetLeft(view, bacterium.X);
            Canvas.SetTop(view, bacterium.Y);
        }

        private void Button_Click_1(object sender, RoutedEventArgs e)
        {
            if (Simulation != null)
            {
                Simulation.EnqueueCommand(new SimulationCommand("pause"));
                PauseButton.IsEnabled = false;
                ResumeButton.IsEnabled = true;
            }
        }

        private void ResumeButton_Click(object sender, RoutedEventArgs e)
        {
            if (Simulation != null)
            {
                Simulation.EnqueueCommand(new SimulationCommand("resume"));
                PauseButton.IsEnabled = true;
                ResumeButton.IsEnabled = false;
            }
        }

        private void StopButton_Click(object sender, RoutedEventArgs e)
        {
            this.MouseMove += MouseMoveForResize;
            this.MouseLeftButtonDown += MouseLeftButtonDownForResize;

            this.SimulationCanvas.MouseLeftButtonDown -= SimulationCanvas_MouseLeftButtonDown;
            this.SimulationCanvas.MouseRightButtonDown -= SimulationCanvas_MouseRightButtonDown;

            guiState = SimulationState.STOPPED;
            if (Simulation != null)
            {
                Simulation.EnqueueCommand(new SimulationCommand("stop"));
                clearCanvas();
            }
            StartButton.IsEnabled = true;
            StopButton.IsEnabled = false;
            AddButton.IsEnabled = false;
            AddCannibalBacteriumButton.IsEnabled = false;
        }

        private void clearCanvas()
        {
            this.SimulationCanvas.Children.Clear();
            //this.viewDictonary.Clear();
        }

        private void AddButton_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                Double speed = Double.Parse(txtCyanobacteriumSpeed.Text); 

                Bacterium bact = new Bacterium(this.Simulation, new SimulationPoint(0, 0), Vector2D.GetRandomVector2D(1),
                    speed, 850, double.Parse(txtInitialEnergy.Text), new CyanobacteriumSimulationStrategy(), double.Parse(txtMaxEnergy.Text), double.Parse(txtCyanoEnergyInc.Text));
                SimulationCommand cmd = new AddCommand(bact);
                AddBacteriumView(bact, null);
                Simulation.EnqueueCommand(cmd);
                bact.Death += bact_Death;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        void bact_Death(BacteriumUpdateEventArgs args)
        {
            Object[] functionArgs = new Object[1];
            functionArgs[0] = args;
            deathDelegateEventHandler d = BacteriumDeath;
            this.Dispatcher.BeginInvoke(d, functionArgs);
        }

        protected void BacteriumDeath(BacteriumUpdateEventArgs args)
        {
            if (viewDictonary.ContainsKey(args.SourceBacterium))
            {
                BacteriumView view = viewDictonary[args.SourceBacterium];
                this.viewDictonary.Remove(args.SourceBacterium);
                this.SimulationCanvas.Children.Remove(view);
            }
        }

        protected BacteriumView AddBacteriumView(Bacterium bacterium, Brush fill)
        {
            BacteriumView view = new BacteriumView();
            if (fill != null)
            {
                view.internalEllipse.Fill = fill;
                view.internalEllipse.Stroke = fill;
            }
            view.Width = bacterium.Width;
            view.Height = bacterium.Height;
            this.SimulationCanvas.Children.Add(view);
            Canvas.SetLeft(view, bacterium.X);
            Canvas.SetTop(view, bacterium.Y);
            viewDictonary.Add(bacterium, view);

            return view;
        }

        protected void RemoveBacteriumView(Bacterium bact)
        {
            if (viewDictonary.ContainsKey(bact))
            {
                BacteriumView view = viewDictonary[bact];
                SimulationCanvas.Children.Remove(view);
                viewDictonary.Remove(bact);
            }
        }

        private void AddCannibalBacteriumButton_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                Double speed = Double.Parse(txtCannibalBacteriumSpeed.Text);

                Bacterium bact = new Bacterium(this.Simulation, new SimulationPoint(0, 0), Vector2D.GetRandomVector2D(1), speed, 850, double.Parse(txtCannibalInitialEnergy.Text), 
                    new CannibalBacteriumStrategy(), double.Parse(txtCannibalMaxEnergy.Text), double.Parse(txtCannibalEnergyInc.Text));
                SimulationCommand cmd = new AddCommand(bact);
                AddBacteriumView(bact, Brushes.Red);
                Simulation.EnqueueCommand(cmd);
                bact.Death += bact_Death;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.StackTrace, ex.Message, MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        private void chkDrawGrid_Checked(object sender, RoutedEventArgs e)
        {
            if (this.Simulation != null)
            {
                if ((bool)chkDrawGrid.IsChecked)
                {
                    DrawGrid();
                }
                else
                {
                    ClearGrid();
                }
            }
        }

        private void Button_Click_2(object sender, RoutedEventArgs e)
        {
            if (this.Simulation != null)
            {
                Simulation.EnqueueCommand(new SimulationCommand("dumpIndex"));
            }
        }

    }
}
